﻿using AMEC.PCSoftware.CommunicationProtocol.CrazyHein.FINS.Message;
using System.Runtime.InteropServices;

namespace AMEC.PCSoftware.CommunicationProtocol.CrazyHein.FINS.Command
{
    public enum IO_MEMORY_AREA_DATA_TYPE_T
    {
        BIT,
        WORD,
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct MEMORY_ACCESS_REQUEST_HEADER_T
    {
        public COMMAND_REQUEST_HEADER_T command;
        public byte area_code;
        public ushort address;
        public byte bit;
        public ushort number_of_data;

        public MEMORY_ACCESS_REQUEST_HEADER_T()
        {
            command = new COMMAND_REQUEST_HEADER_T();
            area_code = 0;
            address = 0;
            bit = 0;
            number_of_data = 0;
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct MEMORY_ACCESS_RESPONSE_HEADER_T
    {
        public COMMAND_REQUEST_HEADER_T command;
        public ushort error_code;

        public MEMORY_ACCESS_RESPONSE_HEADER_T()
        {
            command = new COMMAND_REQUEST_HEADER_T();
            error_code = 0;
        }
    }

    public class MemoryAreaAccess
    {
        private static Dictionary<string, (byte code, IO_MEMORY_AREA_DATA_TYPE_T type)> __CODES;
        static MemoryAreaAccess()
        {
            __CODES = new Dictionary<string, (byte code, IO_MEMORY_AREA_DATA_TYPE_T type)>()
            {
                { "DM@WORD", (0x82, IO_MEMORY_AREA_DATA_TYPE_T.WORD) },
                { "DM@BIT", (0x02, IO_MEMORY_AREA_DATA_TYPE_T.BIT) },
            };
        }

        public static int BUILD_READ_REQUEST(byte destination, byte source, string code, ushort bytePos, byte bitPos, ushort number, ref MESSAGE_T message)
        {
            message.type = Message.Message.COMMAND;
            message.length = Message.Message.SWAP32((uint)(Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>() + 8));
            message.error_code = 0;

            MEMORY_ACCESS_REQUEST_HEADER_T req = new MEMORY_ACCESS_REQUEST_HEADER_T();
            req.command.header.DA1 = destination;
            req.command.header.SA1 = source;
            req.command.main_command = FINS_MAIN_COMMANDS_T.IO_MEMORY_AREA_ACCESS;
            req.command.sub_command = FINS_SUB_COMMANDS_T.READ;
            req.area_code = __CODES[code].code;
            req.address = Message.Message.SWAP16(bytePos);
            req.bit = bitPos;
            req.number_of_data = Message.Message.SWAP16(number);
            MemoryMarshal.Write(message.data, req);

            return Marshal.SizeOf<MESSAGE_HEADER_T>() + Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>();
        }

        public static int BUILD_READ_REQUEST(byte destination, byte source, string code, ushort bytePos, byte bitPos, ushort number, byte[] message)
        {
            MESSAGE_HEADER_T header = new MESSAGE_HEADER_T();
            header.type = Message.Message.COMMAND;
            header.length = Message.Message.SWAP32((uint)(Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>() + 8));
            header.error_code = 0;

            MEMORY_ACCESS_REQUEST_HEADER_T req = new MEMORY_ACCESS_REQUEST_HEADER_T();
            req.command.header.DA1 = destination;
            req.command.header.SA1 = source;
            req.command.main_command = FINS_MAIN_COMMANDS_T.IO_MEMORY_AREA_ACCESS;
            req.command.sub_command = FINS_SUB_COMMANDS_T.READ;
            req.area_code = __CODES[code].code;
            req.address = Message.Message.SWAP16(bytePos);
            req.bit = bitPos;
            req.number_of_data = Message.Message.SWAP16(number);

            MemoryMarshal.Write(message, header);
            MemoryMarshal.Write(message.AsSpan(Marshal.SizeOf<MESSAGE_HEADER_T>()), req);

            return Marshal.SizeOf<MESSAGE_HEADER_T>() + Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>();
        }

        public static int BUILD_WRITE_REQUEST(byte destination, byte source, string code, ushort bytePos, byte bitPos, ushort number, ReadOnlyMemory<ushort> data, byte[] message)
        {
            int total = Marshal.SizeOf<MESSAGE_HEADER_T>() + Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>() + number * 2;
            if (total > Marshal.SizeOf<MESSAGE_T>())
                throw new FINSException(FINS_EXCEPTION_CODE_T.NUM_OF_REQUEST_MEMORY_OUT_OF_RANGE);

            MESSAGE_HEADER_T header = new MESSAGE_HEADER_T();
            header.type = Message.Message.COMMAND;
            header.length = Message.Message.SWAP32((uint)(Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>() + 8 + number * 2));
            header.error_code = 0;

            MEMORY_ACCESS_REQUEST_HEADER_T req = new MEMORY_ACCESS_REQUEST_HEADER_T();
            req.command.header.DA1 = destination;
            req.command.header.SA1 = source;
            req.command.main_command = FINS_MAIN_COMMANDS_T.IO_MEMORY_AREA_ACCESS;
            req.command.sub_command = FINS_SUB_COMMANDS_T.WRITE;
            req.area_code = __CODES[code].code;
            req.address = Message.Message.SWAP16(bytePos);
            req.bit = bitPos;
            req.number_of_data = Message.Message.SWAP16(number);

            MemoryMarshal.Write(message, header);
            MemoryMarshal.Write(message.AsSpan(Marshal.SizeOf<MESSAGE_HEADER_T>()), req);

            int dataPos = Marshal.SizeOf<MESSAGE_HEADER_T>() + Marshal.SizeOf<MEMORY_ACCESS_REQUEST_HEADER_T>();
            for (int i = 0; i < number; ++i)
                MemoryMarshal.Write(message.AsSpan(dataPos + i * 2), Message.Message.SWAP16(data.Span[i]));

            return total;
        }

        public static ushort[]? PARSE_RESPONSE(ReadOnlyMemory<byte> message, out byte destination, out byte source, out FINS_MAIN_COMMANDS_T main, out FINS_SUB_COMMANDS_T sub, out ushort error)
        {
            MESSAGE_HEADER_T header = new MESSAGE_HEADER_T();
            var body = Message.Message.PARSE_RESPONSE(message, out header);
            if (header.type != Message.Message.COMMAND)
                throw new FINSException(FINS_EXCEPTION_CODE_T.RECEIVED_INCORRECT_MEMORY_AREA_ACCESS_RESPONSE);

            if (body.Length < Marshal.SizeOf<MEMORY_ACCESS_RESPONSE_HEADER_T>())
                throw new FINSException(FINS_EXCEPTION_CODE_T.RECEIVED_CORRUPT_MEMORY_AREA_ACCESS_RESPONSE);
            var length = body.Length - Marshal.SizeOf<MEMORY_ACCESS_RESPONSE_HEADER_T>();
            if (length % 2 != 0)
                throw new FINSException(FINS_EXCEPTION_CODE_T.RECEIVED_CORRUPT_MEMORY_AREA_ACCESS_RESPONSE);

            MEMORY_ACCESS_RESPONSE_HEADER_T response = MemoryMarshal.Read<MEMORY_ACCESS_RESPONSE_HEADER_T>(body.Span);
            if((response.command.header.ICF & 0xC0) != 0xC0)
                throw new FINSException(FINS_EXCEPTION_CODE_T.UNEXPECTED_ICF_VALUE);

            destination = response.command.header.DA1;
            source = response.command.header.SA1;
            main = response.command.main_command;
            sub = response.command.sub_command;
            error = Message.Message.SWAP16(response.error_code);

            if(length != 0)
            {
                ushort[] data = new ushort[length / 2];
                for(int i = 0; i < length / 2; i++)
                    data[i] = Message.Message.SWAP16(MemoryMarshal.Read<ushort>(body.Slice(Marshal.SizeOf<MEMORY_ACCESS_RESPONSE_HEADER_T>() + i * 2).Span));
                return data;
            }
            else
                return null;
        }
    }
}
